//	VirtualDub - Video processing and capture application
//	System library component
//	Copyright (C) 1998-2004 Avery Lee, All Rights Reserved.
//
//	Beginning with 1.6.0, the VirtualDub system library is licensed
//	differently than the remainder of VirtualDub.  This particular file is
//	thus licensed as follows (the "zlib" license):
//
//	This software is provided 'as-is', without any express or implied
//	warranty.  In no event will the authors be held liable for any
//	damages arising from the use of this software.
//
//	Permission is granted to anyone to use this software for any purpose,
//	including commercial applications, and to alter it and redistribute it
//	freely, subject to the following restrictions:
//
//	1.	The origin of this software must not be misrepresented; you must
//		not claim that you wrote the original software. If you use this
//		software in a product, an acknowledgment in the product
//		documentation would be appreciated but is not required.
//	2.	Altered source versions must be plainly marked as such, and must
//		not be misrepresented as being the original software.
//	3.	This notice may not be removed or altered from any source
//		distribution.

#include "stdafx.h"
#include <vd2/system/w32assist.h>
#include <vd2/system/text.h>
#include <vd2/system/vdstl.h>

bool VDIsForegroundTaskW32() {
	HWND hwndFore = GetForegroundWindow();

	if (!hwndFore)
		return false;

	DWORD dwProcessId = 0;
	GetWindowThreadProcessId(hwndFore, &dwProcessId);

	return dwProcessId == GetCurrentProcessId();
}

LPVOID VDConvertThreadToFiberW32(LPVOID parm) {
	typedef LPVOID (WINAPI *tpConvertThreadToFiber)(LPVOID p);
	static tpConvertThreadToFiber ctof = (tpConvertThreadToFiber)GetProcAddress(GetModuleHandle("kernel32"), "ConvertThreadToFiber");

	if (!ctof)
		return NULL;

	return ctof(parm);
}

void VDSwitchToFiberW32(LPVOID fiber) {
	typedef void (WINAPI *tpSwitchToFiber)(LPVOID p);
	static tpSwitchToFiber stof = (tpSwitchToFiber)GetProcAddress(GetModuleHandle("kernel32"), "SwitchToFiber");

	if (stof)
		stof(fiber);
}

int VDGetSizeOfBitmapHeaderW32(const BITMAPINFOHEADER *pHdr) {
	int palents = 0;

	if ((pHdr->biCompression == BI_RGB || pHdr->biCompression == BI_RLE4 || pHdr->biCompression == BI_RLE8) && pHdr->biBitCount <= 8) {
		palents = pHdr->biClrUsed;
		if (!palents)
			palents = 1 << pHdr->biBitCount;
	}
	int size = pHdr->biSize + palents * sizeof(RGBQUAD);

	if (pHdr->biSize < sizeof(BITMAPV4HEADER) && pHdr->biCompression == BI_BITFIELDS)
		size += sizeof(DWORD) * 3;

	return size;
}

void VDSetWindowTextW32(HWND hwnd, const wchar_t *s) {
	if (VDIsWindowsNT()) {
		SetWindowTextW(hwnd, s);
	} else {
		SetWindowTextA(hwnd, VDTextWToA(s).c_str());
	}
}

VDStringW VDGetWindowTextW32(HWND hwnd) {
	union {
		wchar_t w[256];
		char a[512];
	} buf;

	if (VDIsWindowsNT()) {
		int len = GetWindowTextLengthW(hwnd);

		if (len > 255) {
			vdblock<wchar_t> tmp(len + 1);
			len = GetWindowTextW(hwnd, tmp.data(), tmp.size());

			VDStringW text(tmp.data(), len);
			return text;
		} else if (len > 0) {
			len = GetWindowTextW(hwnd, buf.w, 256);

			VDStringW text(buf.w, len);
			return text;
		}
	} else {
		int len = GetWindowTextLengthA(hwnd);

		if (len > 511) {
			vdblock<char> tmp(len + 1);
			len = GetWindowTextA(hwnd, tmp.data(), tmp.size());

			VDStringW text(VDTextAToW(tmp.data(), len));
			return text;
		} else if (len > 0) {
			len = GetWindowTextA(hwnd, buf.a, 512);

			VDStringW text(VDTextAToW(buf.a, len));
			return text;
		}
	}

	return VDStringW();
}

void VDAppendMenuW32(HMENU hmenu, UINT flags, UINT id, const wchar_t *text){
	if (VDIsWindowsNT()) {
		AppendMenuW(hmenu, flags, id, text);
	} else {
		AppendMenuA(hmenu, flags, id, VDTextWToA(text).c_str());
	}
}

void VDCheckMenuItemByCommandW32(HMENU hmenu, UINT cmd, bool checked) {
	CheckMenuItem(hmenu, cmd, checked ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);
}

void VDCheckRadioMenuItemByCommandW32(HMENU hmenu, UINT cmd, bool checked) {
	MENUITEMINFOA mii;

	mii.cbSize = sizeof(MENUITEMINFOA);
	mii.fMask = MIIM_FTYPE | MIIM_STATE;
	if (GetMenuItemInfo(hmenu, cmd, FALSE, &mii)) {
		mii.fType |= MFT_RADIOCHECK;
		mii.fState &= ~MFS_CHECKED;
		if (checked)
			mii.fState |= MFS_CHECKED;
		SetMenuItemInfo(hmenu, cmd, FALSE, &mii);
	}
}

void VDEnableMenuItemByCommandW32(HMENU hmenu, UINT cmd, bool checked) {
	EnableMenuItem(hmenu, cmd, checked ? MF_BYCOMMAND|MF_ENABLED : MF_BYCOMMAND|MF_GRAYED);
}

void VDSetMenuItemTextByCommandW32(HMENU hmenu, UINT cmd, const wchar_t *text) {
	if (VDIsWindowsNT()) {
		MENUITEMINFOW mmiW;

		mmiW.cbSize		= MENUITEMINFO_SIZE_VERSION_400W;
		mmiW.fMask		= MIIM_TYPE;
		mmiW.fType		= MFT_STRING;
		mmiW.dwTypeData	= (LPWSTR)text;

		SetMenuItemInfoW(hmenu, cmd, FALSE, &mmiW);
	} else {
		MENUITEMINFOA mmiA;
		VDStringA textA(VDTextWToA(text));

		mmiA.cbSize		= MENUITEMINFO_SIZE_VERSION_400A;
		mmiA.fMask		= MIIM_TYPE;
		mmiA.fType		= MFT_STRING;
		mmiA.dwTypeData	= (LPSTR)textA.c_str();

		SetMenuItemInfoA(hmenu, cmd, FALSE, &mmiA);
	}
}

LRESULT	VDDualCallWindowProcW32(WNDPROC wp, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	return (IsWindowUnicode(hwnd) ? CallWindowProcW : CallWindowProcA)(wp, hwnd, msg, wParam, lParam);
}

LRESULT VDDualDefWindowProcW32(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	return IsWindowUnicode(hwnd) ? DefWindowProcW(hwnd, msg, wParam, lParam) : DefWindowProcA(hwnd, msg, wParam, lParam);
}

EXECUTION_STATE VDSetThreadExecutionStateW32(EXECUTION_STATE esFlags) {
	EXECUTION_STATE es = 0;

	// SetThreadExecutionState(): requires Windows 98+/2000+.
	typedef EXECUTION_STATE (WINAPI *tSetThreadExecutionState)(EXECUTION_STATE);
	static tSetThreadExecutionState pFunc = (tSetThreadExecutionState)GetProcAddress(GetModuleHandle("kernel32"), "SetThreadExecutionState");

	if (pFunc)
		es = pFunc(esFlags);

	return es;
}

bool VDSetFilePointerW32(HANDLE h, sint64 pos, DWORD dwMoveMethod) {
	LONG posHi = (LONG)(pos >> 32);
	DWORD result = SetFilePointer(h, (LONG)pos, &posHi, dwMoveMethod);

	if (result != INVALID_SET_FILE_POINTER)
		return true;

	DWORD dwError = GetLastError();

	return (dwError == NO_ERROR);
}

bool VDGetFileSizeW32(HANDLE h, sint64& size) {
	DWORD dwSizeHigh;
	DWORD dwSizeLow = GetFileSize(h, &dwSizeHigh);

	if (dwSizeLow == (DWORD)-1 && GetLastError() != NO_ERROR)
		return false;

	size = dwSizeLow + ((sint64)dwSizeHigh << 32);
	return true;
}

#if !defined(_MSC_VER) || _MSC_VER < 1300
HMODULE VDGetLocalModuleHandleW32() {
	MEMORY_BASIC_INFORMATION meminfo;
	static HMODULE shmod = (VirtualQuery(&VDGetLocalModuleHandleW32, &meminfo, sizeof meminfo), (HMODULE)meminfo.AllocationBase);

	return shmod;
}
#endif

bool VDDrawTextW32(HDC hdc, const wchar_t *s, int nCount, LPRECT lpRect, UINT uFormat) {
	RECT r;
	if (VDIsWindowsNT()) {
		// If multiline and vcentered (not normally supported...)
		if (!((uFormat ^ DT_VCENTER) & (DT_VCENTER|DT_SINGLELINE))) {
			uFormat &= ~DT_VCENTER;

			r = *lpRect;
			if (!DrawTextW(hdc, s, nCount, &r, uFormat | DT_CALCRECT))
				return false;

			int dx = ((lpRect->right - lpRect->left) - (r.right - r.left)) >> 1;
			int dy = ((lpRect->bottom - lpRect->top) - (r.bottom - r.top)) >> 1;

			r.left += dx;
			r.right += dx;
			r.top += dy;
			r.bottom += dy;
			lpRect = &r;
		}

		return !!DrawTextW(hdc, s, nCount, lpRect, uFormat);
	} else {
		VDStringA strA(VDTextWToA(s, nCount));

		// If multiline and vcentered (not normally supported...)
		if (!((uFormat ^ DT_VCENTER) & (DT_VCENTER|DT_SINGLELINE))) {
			uFormat &= ~DT_VCENTER;

			r = *lpRect;
			if (!DrawTextA(hdc, strA.data(), strA.size(), &r, uFormat | DT_CALCRECT))
				return false;

			int dx = ((lpRect->right - lpRect->left) - (r.right - r.left)) >> 1;
			int dy = ((lpRect->bottom - lpRect->top) - (r.bottom - r.top)) >> 1;

			r.left += dx;
			r.right += dx;
			r.top += dy;
			r.bottom += dy;
			lpRect = &r;
		}

		return !!DrawTextA(hdc, strA.data(), strA.size(), lpRect, uFormat);
	}
}

bool VDPatchModuleImportTableW32(HMODULE hmod, const char *srcModule, const char *name, void *pCompareValue, void *pNewValue, void *volatile *ppOldValue) {
	char *pBase = (char *)hmod;

	__try {
		// The PEheader offset is at hmod+0x3c.  Add the size of the optional header
		// to step to the section headers.

		const uint32 peoffset = ((const long *)pBase)[15];
		const uint32 signature = *(uint32 *)(pBase + peoffset);

		if (signature != IMAGE_NT_SIGNATURE)
			return false;

		const IMAGE_FILE_HEADER *pHeader = (const IMAGE_FILE_HEADER *)(pBase + peoffset + 4);

		// Verify the PE optional structure.

		if (pHeader->SizeOfOptionalHeader < 104)
			return false;

		// Find import header.

		const IMAGE_IMPORT_DESCRIPTOR *pImportDir;
		int nImports;

		switch(*(short *)((char *)pHeader + IMAGE_SIZEOF_FILE_HEADER)) {

#ifdef _M_AMD64
		case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
			{
				const IMAGE_OPTIONAL_HEADER64 *pOpt = (IMAGE_OPTIONAL_HEADER64 *)((const char *)pHeader + sizeof(IMAGE_FILE_HEADER));

				if (pOpt->NumberOfRvaAndSizes < 2)
					return false;

				pImportDir = (const IMAGE_IMPORT_DESCRIPTOR *)(pBase + pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
				nImports = pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size / sizeof(IMAGE_IMPORT_DESCRIPTOR);
			}
			break;
#else
		case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
			{
				const IMAGE_OPTIONAL_HEADER32 *pOpt = (IMAGE_OPTIONAL_HEADER32 *)((const char *)pHeader + sizeof(IMAGE_FILE_HEADER));

				if (pOpt->NumberOfRvaAndSizes < 2)
					return false;

				pImportDir = (const IMAGE_IMPORT_DESCRIPTOR *)(pBase + pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
				nImports = pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size / sizeof(IMAGE_IMPORT_DESCRIPTOR);
			}
			break;
#endif

		default:		// reject PE32+
			return false;
		}

		// Hmmm... no imports?

		if ((const char *)pImportDir == pBase)
			return false;

		// Scan down the import entries.  We are looking for MSVFW32.

		int i;

		for(i=0; i<nImports; ++i) {
			if (!_stricmp(pBase + pImportDir[i].Name, srcModule))
				break;
		}

		if (i >= nImports)
			return false;

		// Found it.  Start scanning MSVFW32 imports until we find DrawDibDraw.

		const long *pImports = (const long *)(pBase + pImportDir[i].OriginalFirstThunk);
		void * volatile *pVector = (void * volatile *)(pBase + pImportDir[i].FirstThunk);

		while(*pImports) {
			if (*pImports >= 0) {
				const char *pName = pBase + *pImports + 2;

				if (!strcmp(pName, name)) {

					// Found it!  Reset the protection.

					DWORD dwOldProtect;

					if (VirtualProtect((void *)pVector, sizeof(void *), PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
						if (ppOldValue) {
							for(;;) {
								void *old = *pVector;
								if (pCompareValue && pCompareValue != old)
									return false;

								*ppOldValue = old;
								if (old == VDAtomicCompareExchangePointer(pVector, pNewValue, old))
									break;
							}
						} else {
							*pVector = pNewValue;
						}

						VirtualProtect((void *)pVector, sizeof(void *), dwOldProtect, &dwOldProtect);

						return true;
					}

					break;
				}
			}

			++pImports;
			++pVector;
		}
	} __except(1) {
	}

	return false;
}
